﻿using OkTool.Date;
using OkTool.Regex;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace OkTool.Util
{
    public class DateUtil
    {
        /// <summary>
        /// java.util.Date EEE MMM zzz 缩写数组
        /// </summary>
        private static String[] wtb = { //
			"sun", "mon", "tue", "wed", "thu", "fri", "sat", // 星期
			"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec", // 月份
			"gmt", "ut", "utc", "est", "edt", "cst", "cdt", "mst", "mdt", "pst", "pdt"// 时间标准
	};
        /// <summary>
        /// 生日转为年龄，计算法定年龄
        /// </summary>
        /// <param name="birthDay">生日，标准日期字符串</param>
        /// <returns></returns>
        public static int AgeOfNow(string birthDay)
        {
            return AgeOfNow(Parse(birthDay).Value);
        }
        /// <summary>
        /// 将日期字符串转换为DateTime格式：
        /// <list type="number">
        ///<item>yyyy-MM-dd HH:mm:ss</item>
        ///<item>yyyy/MM/dd HH:mm:ss</item>
        ///<item>yyyy.MM.dd HH:mm:ss</item>
        ///<item>yyyy年MM月dd日 HH时mm分ss秒</item>
        ///<item>yyyy-MM-dd</item>
        ///<item>yyyy/MM/dd</item>
        ///<item>yyyy.MM.dd</item>
        ///<item>HH:mm:ss</item>
        ///<item>HH时mm分ss秒</item>
        ///<item>yyyy-MM-dd HH:mm</item>
        ///<item>yyyy-MM-dd HH:mm:ss.SSS</item>
        ///<item>yyyy-MM-dd HH:mm:ss.SSSSSS</item>
        ///<item>yyyyMMddHHmmss</item>
        ///<item>yyyyMMddHHmmssSSS</item>
        ///<item>yyyyMMdd</item>
        ///<item>EEE, dd MMM yyyy HH:mm:ss z</item>
        ///<item>EEE MMM dd HH:mm:ss zzz yyyy</item>
        ///<item>yyyy-MM-dd'T'HH:mm:ss'Z'</item>
        ///<item>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</item>
        ///<item>yyyy-MM-dd'T'HH:mm:ssZ</item>
        ///<item>yyyy-MM-dd'T'HH:mm:ss.SSSZ</item>  
        ///</list>
        /// </summary>
        /// <param name="dateStr"></param>
        /// <returns></returns>
        public static DateTime? Parse(string dateStr)
        {
            if (StrUtil.IsBlank(dateStr))
            {
                return null;
            }
            dateStr = dateStr.Replace("日", "");
            dateStr = dateStr.Replace("秒", "");
            int length = dateStr.Length;
            if (NumberUtil.IsNumber(dateStr))
            {
                //纯数字
                if (length == DatePattern.PURE_DATETIME_PATTERN.Length)
                {
                    return Parse(dateStr, DatePattern.PURE_DATETIME_PATTERN);
                }
                else if (length == DatePattern.PURE_DATETIME_MS_PATTERN.Length)
                {
                    return Parse(dateStr, DatePattern.PURE_DATETIME_MS_PATTERN);
                }
                else if (length == DatePattern.PURE_DATE_PATTERN.Length)
                {
                    return Parse(dateStr, DatePattern.PURE_DATE_PATTERN);
                }
                else if (length == DatePattern.PURE_TIME_PATTERN.Length)
                {
                    return Parse(dateStr, DatePattern.PURE_TIME_PATTERN);
                }
            }
            else if (RegexUtil.IsMatch(dateStr, RegexPool.TIME))
            {
                // HH:mm:ss 或者 HH:mm 时间格式匹配单独解析
                return ParseTimeToday(dateStr);
            }
            else if (StrUtil.ContainsAnyIgnoreCase(dateStr, wtb))
            {
                // JDK的Date对象toString默认格式，类似于：
                // Tue Jun 4 16:25:15 +0800 2019
                // Thu May 16 17:57:18 GMT+08:00 2019
                // Wed Aug 01 00:00:00 CST 2012
            }
            //标准日期格式（包括单个数字的日期时间）
            dateStr = Normalize(dateStr);
            if (RegexUtil.IsMatch(DatePattern.REGEX_NORM, dateStr))
            {
                int colonCount = StrUtil.Count(dateStr, Text.CharPool.COLON);
                switch (colonCount)
                {
                    case 0:
                        // yyyy-MM-dd
                        return Parse(dateStr, DatePattern.NORM_DATE_PATTERN);
                    case 1:
                        // yyyy-MM-dd HH:mm
                        return Parse(dateStr, DatePattern.NORM_DATETIME_MINUTE_PATTERN);
                    case 2:
                        int indexOfDot = dateStr.IndexOf(CharUtil.DOT);
                        if (indexOfDot > 0)
                        {
                            int length1 = dateStr.Length;
                            // yyyy-MM-dd HH:mm:ss.SSS 或者 yyyy-MM-dd HH:mm:ss.SSSSSS
                            if (length1 - indexOfDot > 4)
                            {
                                // 类似yyyy-MM-dd HH:mm:ss.SSSSSS，采取截断操作
                                dateStr = StrUtil.SubPre(dateStr, indexOfDot + 4);
                            }
                            return Parse(dateStr, DatePattern.NORM_DATETIME_MS_PATTERN);
                        }
                        // yyyy-MM-dd HH:mm:ss
                        return Parse(dateStr, DatePattern.NORM_DATETIME_PATTERN);
                }
            }
            return null;
        }

        /// <summary>
        /// 标准化日期，默认处理以空格区分的日期时间格式，空格前为日期，空格后为时间：
        /// <para>将以下字符替换为"-"</para>
        /// <para>"."</para>
        /// <para>"/"</para>
        /// <para>"年"</para>
        /// <para>"月"</para>
        /// <para>将以下字符去除</para>
        /// <para>"日"</para>
        /// <para>将以下字符替换为":"</para>
        /// <para>"时"</para>
        /// <para>"分"</para>
        /// <para>"秒"</para>
        /// <para>当末位是":"时去除之（不存在毫秒时）</para>
        /// </summary>
        /// <param name="dateStr"></param>
        /// <returns></returns>
        public static string Normalize(string dateStr)
        {
            if (string.IsNullOrWhiteSpace(dateStr))
            {
                return dateStr;
            }

            // 日期时间分开处理
            List<string> dateAndTime = new List<string>(dateStr.Split(' '));
            int size = dateAndTime.Count;
            if (size < 1 || size > 2)
            {
                // 非可被标准处理的格式
                return dateStr;
            }

            // StringBuilder替换为StringBuilder
            System.Text.StringBuilder builder = new System.Text.StringBuilder();

            // 日期部分（"\"、"/"、"."、"年"、"月"都替换为"-"）
            string datePart = System.Text.RegularExpressions.Regex.Replace(dateAndTime[0], "[/.年月]", "-");
            datePart = datePart.TrimEnd('日');
            builder.Append(datePart);

            // 时间部分
            if (size == 2)
            {
                builder.Append(' ');
                string timePart = System.Text.RegularExpressions.Regex.Replace(dateAndTime[1], "[时分秒]", ":");
                timePart = timePart.TrimEnd(':');
                // 将ISO8601中的逗号替换为.
                timePart = timePart.Replace(',', '.');
                builder.Append(timePart);
            }

            return builder.ToString();
        }
        /// <summary>
        ///  解析时间，格式HH:mm 或 HH:mm:ss，日期默认为今天
        /// </summary>
        /// <param name="timeString"></param>
        /// <returns></returns>
        public static DateTime ParseTimeToday(string timeString)
        {
            timeString = string.Format("{0} {1}", Today(), timeString);
            if (1 == StrUtil.Count(timeString, ':'))
            {
                // 时间格式为 HH:mm
                return Parse(timeString, DatePattern.NORM_DATETIME_MINUTE_PATTERN);
            }
            else
            {
                // 时间格式为 HH:mm:ss
                return Parse(timeString, DatePattern.NORM_DATETIME_PATTERN);
            }
        }
        /// <summary>
        /// 生日转为年龄，计算法定年龄
        /// </summary>
        /// <param name="birthDay">生日</param>
        /// <returns></returns>
        public static int AgeOfNow(DateTime birthDay)
        {
            return Age(birthDay, DateTime.Now);
        }
        /// <summary>
        /// 计算相对于dateToCompare的年龄，常用于计算指定生日在某年的年龄
        /// </summary>
        /// <param name="birthday"></param>
        /// <param name="dateToCompare"></param>
        /// <returns></returns>
        public static int Age(DateTime birthday, DateTime dateToCompare)
        {

            if (null == dateToCompare)
            {
                dateToCompare = DateTime.Now;
            }
            return Age(GetTimeStamp13(birthday), GetTimeStamp13(dateToCompare));
        }

        /// <summary>
        /// 计算相对于dateToCompare的年龄，常用于计算指定生日在某年的年龄
        /// <para>按照《最高人民法院关于审理未成年人刑事案件具体应用法律若干问题的解释》第二条规定刑法第十七条规定的“周岁”，按照公历的年、月、日计算，从周岁生日的第二天起算。</para>
        /// <list type="table">
        /// <item>2022-03-01出生，则相对2023-03-01，周岁为0，相对于2023-03-02才是1岁。</item>
        /// <item>1999-02-28出生，则相对2000-02-29，周岁为1</item>
        /// </list>
        /// </summary>
        /// <param name="birthday"></param>
        /// <param name="dateToCompare"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        public static int Age(long birthday, long dateToCompare)
        {
            if (birthday > dateToCompare)
            {
                throw new ArgumentException("Birthday is after dateToCompare!");
            }

            var cal = TimeStampToDateTime(dateToCompare.ToString());

            var year = cal.Year;
            var month = cal.Month;
            var dayOfMonth = cal.Day;

            // 复用 cal
            cal = TimeStampToDateTime((birthday).ToString());
            var age = year - cal.Year;

            // 当前日期，则为0岁
            if (age == 0)
            {
                return 0;
            }

            var monthBirth = cal.Month;
            if (month == monthBirth)
            {
                var dayOfMonthBirth = cal.Day;
                // issue#I6E6ZG，法定生日当天不算年龄，从第二天开始计算
                if (dayOfMonth <= dayOfMonthBirth)
                {
                    // 如果生日在当月，但是未达到生日当天的日期，年龄减一
                    age--;
                }
            }
            else if (month < monthBirth)
            {
                // 如果当前月份未达到生日的月份，年龄计算减一
                age--;
            }

            return age;
        }

        /// <summary>
        /// 是否闰年
        /// </summary>
        /// <param name="year">年</param>
        /// <returns></returns>
        public static bool IsLeapYear(int year)
        {
            return DateTime.IsLeapYear(year);
        }
        /// <summary>
        /// 将特定格式的日期转换为DateTime对象
        /// </summary>
        /// <param name="dateStr"></param>
        /// <param name="format"></param>
        /// <returns></returns>
        public static DateTime Parse(string dateStr, string format)
        {
            return DateTime.ParseExact(dateStr, format, null);
        }

        /// <summary>
        /// 今年
        /// </summary>
        /// <returns></returns>
        public static int ThisYear()
        {
            return Year(DateTime.Now);
        }

        #region 时间戳

        /// <summary>        
        /// 时间戳转为C#格式时间        
        /// </summary>        
        /// <param name=”timeStamp”></param>        
        /// <returns></returns>        
        public static DateTime TimeStampToDateTime(string timeStamp)
        {
            DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1, 0, 0, 0, 0));

            long t = long.Parse(timeStamp + "0000");

            TimeSpan timeSpan = new TimeSpan(t);
            DateTime nowTime = startTime.Add(timeSpan);
            return nowTime;
        }

        /// <summary>
        /// 取十三位时间戳 
        /// </summary>
        /// <param name="nowTime">给的时间值</param>
        /// <returns>返回13位时间戳</returns>
        public static long GetTimeStamp13(DateTime nowTime)
        {
            DateTime startTime = TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1, 0, 0, 0, 0));

            long unixTime = (long)Math.Round((nowTime - startTime).TotalMilliseconds, MidpointRounding.AwayFromZero);
            //long t = (time.Ticks - startTime.Ticks) / 10000;
            return unixTime;
        }

        /// <summary>
        /// 取13位时间戳
        /// </summary>
        /// <returns>返回13位时间戳</returns>
        public static long GetTimeStamp13()
        {
            return GetTimeStamp13(DateTime.Now);
        }

        /// <summary>
        /// 获得现在的10位时间戳
        /// </summary>
        /// <returns></returns>
        public static long GetTimeStamp10()
        {
            return GetTimeStamp10(DateTime.Now);
        }

        /// <summary>
        /// 获得指定时间的10位时间戳
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static long GetTimeStamp10(DateTime dateTime)
        {
            TimeSpan ts = dateTime.ToUniversalTime() - new DateTime(1970, 1, 1);//ToUniversalTime()转换为标准时区的时间,去掉的话直接就用北京时间
            //return (long)ts.TotalMilliseconds; //精确到毫秒
            return (long)ts.TotalSeconds;//获取10位
        }
        #endregion

        #region 周
        /// <summary>
        /// 获取指定年份有多少周
        /// </summary>
        /// <param name="year">年份</param>
        /// <returns>该年周数</returns>
        public static int WeeksInYear(int year)
        {
            var end = new System.DateTime(year, 12, 31); //该年最后一天
            var gc = new GregorianCalendar();
            return gc.GetWeekOfYear(end, CalendarWeekRule.FirstDay, System.DayOfWeek.Monday); //该年星期数
        }

        /// <summary>
        /// 获得指定日期是所在年份的第几周,默认星期日是第一天
        /// </summary>
        /// <param name="date">时间</param>
        /// <returns>第几周</returns>
        public static int WeekOfYear(System.DateTime date)
        {
            var gc = new GregorianCalendar();
            return gc.GetWeekOfYear(date, CalendarWeekRule.FirstDay, System.DayOfWeek.Sunday);
        }

        /// <summary>
        /// 获得指定日期是所在年份的第几周
        /// </summary>
        /// <param name="date">时间</param>
        /// <param name="week">一周的开始日期</param>
        /// <returns>第几周</returns>
        public static int WeekOfYear(System.DateTime date, DayOfWeek week)
        {
            var gc = new GregorianCalendar();
            return gc.GetWeekOfYear(date, CalendarWeekRule.FirstDay, week);
        }

        /// <summary>
        /// 得到一年中的某周的起始日和截止日
        /// 年 nYear
        /// 周数 nNumWeek
        /// 周始 out dtWeekStart
        /// 周终 out dtWeekeEnd
        /// </summary>

        /// <param name="nYear">年份</param>
        /// <param name="nNumWeek">第几周</param>
        /// <param name="dtWeekStart">开始日期</param>
        /// <param name="dtWeekeEnd">结束日期</param>
        public static void GetWeekTime(int nYear, int nNumWeek, out System.DateTime dtWeekStart, out System.DateTime dtWeekeEnd)
        {
            var dt = new System.DateTime(nYear, 1, 1);
            dt += new TimeSpan((nNumWeek - 1) * 7, 0, 0, 0);
            dtWeekStart = dt.AddDays(-(int)dt.DayOfWeek + (int)System.DayOfWeek.Monday);
            dtWeekeEnd = dt.AddDays((int)System.DayOfWeek.Saturday - (int)dt.DayOfWeek + 1);
        }

        /// <summary>
        /// 得到一年中的某周的起始日和截止日    周一到周五  工作日
        /// </summary>
        /// <param name="_"></param>
        /// <param name="nYear">年份</param>
        /// <param name="nNumWeek">第几周</param>
        /// <param name="dtWeekStart">开始日期</param>
        /// <param name="dtWeekeEnd">结束日期</param>
        public static void GetWeekWorkTime(int nYear, int nNumWeek, out System.DateTime dtWeekStart, out System.DateTime dtWeekeEnd)
        {
            var dt = new System.DateTime(nYear, 1, 1);
            dt += new TimeSpan((nNumWeek - 1) * 7, 0, 0, 0);
            dtWeekStart = dt.AddDays(-(int)dt.DayOfWeek + (int)System.DayOfWeek.Monday);
            dtWeekeEnd = dt.AddDays((int)System.DayOfWeek.Saturday - (int)dt.DayOfWeek + 1).AddDays(-2);
        }

        /// <summary>
        /// 是否为本月的最后一天
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static bool IsLastDayOfMonth(DateTime dateTime)
        {
            return DateTime.DaysInMonth(dateTime.Year, dateTime.Month) == dateTime.Day;
        }

        /// <summary>
        /// 获得指定日期是这个日期所在年的第几天
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static int DayOfYear(DateTime date)
        {
            return date.DayOfYear;
        }

        /// <summary>
        ///获得本月的最后一天 
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static int GetLastDayOfMonth(DateTime dateTime)
        {
            return DateTime.DaysInMonth(dateTime.Year, dateTime.Month);
        }


        /// <summary>返回当前日期的星期名称</summary>
        /// <param name="idt">日期</param>
        /// <returns>星期名称</returns>
        public static string DayOfWeekName(System.DateTime idt)
        {
            return idt.DayOfWeek.ToString() switch
            {
                "Mondy" => "星期一",
                "Tuesday" => "星期二",
                "Wednesday" => "星期三",
                "Thursday" => "星期四",
                "Friday" => "星期五",
                "Saturday" => "星期六",
                "Sunday" => "星期日",
                _ => ""
            };
        }

        /// <summary>获得指定日期是星期几,1表示周一,0表示周日</summary>
        /// <param name="idt">日期</param>
        /// <returns>星期数字编号</returns>
        public static int DayOfWeek(System.DateTime idt)
        {
            return idt.DayOfWeek.ToString() switch
            {
                "Mondy" => 1,
                "Tuesday" => 2,
                "Wednesday" => 3,
                "Thursday" => 4,
                "Friday" => 5,
                "Saturday" => 6,
                "Sunday" => 0,
                _ => 0
            };
        }

        /// <summary>
        /// 根据指定格式格式化日期
        /// </summary>
        /// <param name="date">被格式化的日期</param>
        /// <param name="format">日期格式，常用格式见：DatePattern</param>
        /// <returns></returns>
        public static string Format(DateTime? date, string format)
        {
            if (date.HasValue == false || string.IsNullOrEmpty(format))
            {
                return null;
            }
            return date?.ToString(format);
        }


        /// <summary>
        /// 获得指定日期是这个日期所在月份的第几天
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static int DayOfMonth(DateTime date)
        {
            return date.Day;
        }

        /// <summary>
        /// 获得指定日期所在月份有多少天
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static int DaysInMonth(DateTime date)
        {
            return DateTime.DaysInMonth(date.Year, date.Month);
        }
        /// <summary>
        /// 获得指定日期是星期几
        /// </summary>
        /// <param name="idt"></param>
        /// <returns></returns>
        public static System.DayOfWeek DayOfWeekEnum(System.DateTime idt)
        {
            return idt.DayOfWeek;
        }

        /// <summary>
        /// 是否为周末(周六或周日)
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static bool IsWeekEnd(DateTime date)
        {
            return date.DayOfWeek == System.DayOfWeek.Saturday || date.DayOfWeek == System.DayOfWeek.Sunday;
        }

        /// <summary>
        /// 获得指定日期在所在月份的第几周
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static bool WeekOfMonth(DateTime date)
        {
            return date.DayOfWeek == System.DayOfWeek.Saturday || date.DayOfWeek == System.DayOfWeek.Sunday;
        }



        #endregion
        /// <summary>
        /// 检查两个时间段是否有时间重叠
        /// </summary>
        /// <param name="startA">第一个2023-01-10</param>
        /// <param name="endA">第一个2023-01-15</param>
        /// <param name="startB">第二个2023-01-01</param>
        /// <param name="endB">第二个2023-01-16</param>
        /// <returns></returns>
        public static bool ISOverlap(DateTime? startA, DateTime? endA, DateTime? startB, DateTime? endB)
        {
            if (!startA.HasValue || !endA.HasValue || !startB.HasValue || !endB.HasValue)
            {
                return false;
            }
            //2023-01-10,2023-01-15与2023-01-01,2023-01-16
            //2023-01-01,2023-01-16与2023-01-10,2023-01-15
            return startA < endB && startB < endA;
        }



        /// <summary>
        /// 当前时间,格式 yyyy-MM-dd HH:mm:ss
        /// </summary>
        /// <returns></returns>
        public static string Now()
        {
            return FormatDateTime(DateTime.Now);
        }

        /// <summary>
        /// 当前日期,格式 yyyy-MM-dd
        /// </summary>
        /// <returns></returns>
        public static string Today()
        {
            return FormatDate(DateTime.Now);
        }

        /// <summary>
        /// 格式化日期时间
        /// <para>格式 yyyy-MM-dd HH:mm:ss</para>
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static string FormatDateTime(DateTime? date)
        {
            if (date.HasValue == false)
            {
                return null;
            }
            return date.Value.ToString("yyyy-MM-dd HH:mm:ss");
        }

        /// <summary>
        /// 格式化日期部分(不包括时间)
        /// <para>格式 yyyy-MM-dd</para>
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static string FormatDate(DateTime? date)
        {
            if (date.HasValue == false)
            {
                return null;
            }
            return date.Value.ToString("yyyy-MM-dd");
        }

        /// <summary>
        /// 格式化时间
        /// <para>格式 HH:mm:ss</para>
        /// </summary>
        /// <param name="date"></param>
        /// <returns></returns>
        public static string FormatTime(DateTime? date)
        {
            if (date.HasValue == false)
            {
                return null;
            }
            return date.Value.ToString("HH:mm:ss");
        }

        /// <summary>
        /// 获得年的部分
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static int Year(DateTime dateTime)
        {
            return dateTime.Year;
        }

        /// <summary>
        /// 获得指定日期的所属季节,从1开始计数
        /// </summary>
        /// <param name="dateTime"></param>
        /// <returns></returns>
        public static int Quarter(DateTime dateTime)
        {
            int year = dateTime.Year;
            int jd;
            DateTime dt0 = new DateTime(year, 1, 1);
            DateTime dt1 = new DateTime(year, 4, 1);
            DateTime dt2 = new DateTime(year, 7, 1);
            DateTime dt3 = new DateTime(year, 10, 1);
            if (dateTime.CompareTo(dt1) < 0)
                jd = 1;
            else if (dateTime.CompareTo(dt2) < 0)
                jd = 2;
            else if (dateTime.CompareTo(dt3) < 0)
                jd = 3;
            else
                jd = 4;
            return jd;
        }
    }
}
